from django.conf import settings
from enum import Enum, unique

import json
import time
import uuid
import hashlib
from base64 import b64decode, b64encode

from cryptography.exceptions import InvalidSignature, InvalidTag
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP, PKCS1v15
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SM3, Hash
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate
from cryptography import __version__ as cryptography_version


# private_key = open(settings.BASE_DIR / "conf/WePay/apiclient_key.pem").read()
# apiv3 = "K35WKqXHOZdKGbikq4nER4vtgVCWydkJ"

private_key = open(settings.BASE_DIR / "conf/yhhj/apiclient_key.pem").read()
# apiv3 = "xdSKt5a8O1lQ2M6ThPzQuu8IloyjejyZ"


@unique
class RequestType(Enum):
    GET = "GET"
    POST = "POST"
    PATCH = "PATCH"
    PUT = "PUT"
    DELETE = "DELETE"


class WeChatPayType(Enum):
    JSAPI = 0
    APP = 1
    H5 = 2
    NATIVE = 3
    MINIPROG = 4


class SignType(Enum):
    RSA_SHA256 = 0
    HMAC_SHA256 = 1
    MD5 = 2

    # ===================================


def format_private_key(private_key_str):
    pem_start = "-----BEGIN PRIVATE KEY-----\n"
    pem_end = "\n-----END PRIVATE KEY-----"
    if not private_key_str.startswith(pem_start):
        private_key_str = pem_start + private_key_str
    if not private_key_str.endswith(pem_end):
        private_key_str = private_key_str + pem_end
    return private_key_str


def load_private_key(private_key_str):
    try:
        return load_pem_private_key(
            data=format_private_key(private_key_str).encode("UTF-8"),
            password=None,
            backend=default_backend(),
        )
    except:
        raise Exception("failed to load private key.")


def load_certificate(certificate_str):
    try:
        return load_pem_x509_certificate(data=certificate_str.encode("UTF-8"), backend=default_backend())
    except:
        return None


# ============================================================
def md5_sign(sign_str):
    sign = hashlib.md5(sign_str.encode("UTF-8")).hexdigest().upper()
    return sign


def hmac_sign(key, sign_str):
    hmac = HMAC(key.encode("UTF-8"), SHA256())
    hmac.update(sign_str.encode("UTF-8"))
    sign = hmac.finalize().hex().upper()
    return sign


def rsa_sign(private_key, sign_str):
    message = sign_str.encode("UTF-8")
    signature = private_key.sign(data=message, padding=PKCS1v15(), algorithm=SHA256())
    sign = b64encode(signature).decode("UTF-8").replace("\n", "")
    return sign


def rsa_verify(timestamp, nonce, body, signature, certificate):
    sign_str = "%s\n%s\n%s\n" % (timestamp, nonce, body)
    certificate = load_certificate(certificate)
    public_key = certificate.public_key()
    message = sign_str.encode("UTF-8")
    try:
        signature = b64decode(signature)
    except:
        return False
    try:
        public_key.verify(signature, message, PKCS1v15(), SHA256())
    except InvalidSignature:
        return False
    return True


def doSign_RSA(data, key=None):
    """准备证书签名 RSA_SHA256"""
    _private_key = load_private_key(key) if key else load_private_key(private_key)
    sign_str = "\n".join(data) + "\n"
    # print("签名字符v3:\n", sign_str)
    return rsa_sign(_private_key, sign_str)


def doSign_HMAC(data, key=None):
    """准备APIv3签名 HMAC_SHA256"""
    _apiv3_key = key
    key_list = sorted(data.keys())
    sign_str = ""
    for k in key_list:
        v = data[k]
        sign_str += str(k) + "=" + str(v) + "&"
    sign_str += "key=" + _apiv3_key
    return hmac_sign(_apiv3_key, sign_str)


def doSign_MD5(data, key=None):
    """准备APIv3签名 MD5"""
    _apiv3_key = key
    key_list = sorted(data.keys())
    sign_str = ""
    for k in key_list:
        v = data[k]
        sign_str += str(k) + "=" + str(v) + "&"
    sign_str += "key=" + _apiv3_key
    return md5_sign(sign_str)


def aes_decrypt(nonce, ciphertext, associated_data, apiv3_key):
    """AES解密"""
    key_bytes = apiv3_key.encode("UTF-8")
    nonce_bytes = nonce.encode("UTF-8")
    associated_data_bytes = associated_data.encode("UTF-8")
    data = b64decode(ciphertext)
    aesgcm = AESGCM(key=key_bytes)
    try:
        result = aesgcm.decrypt(nonce=nonce_bytes, data=data, associated_data=associated_data_bytes).decode(
            "UTF-8"
        )
    except InvalidTag:
        result = None
    return result


def rsa_encrypt(text, certificate):
    """RSA加密"""
    data = text.encode("UTF-8")
    public_key = certificate.public_key()
    cipherbyte = public_key.encrypt(
        plaintext=data,
        padding=OAEP(mgf=MGF1(algorithm=SHA1()), algorithm=SHA1(), label=None),
    )
    return b64encode(cipherbyte).decode("UTF-8")


def rsa_decrypt(ciphertext, private_key):
    """RSA解密"""
    data = private_key.decrypt(
        ciphertext=b64decode(ciphertext),
        padding=OAEP(mgf=MGF1(algorithm=SHA1()), algorithm=SHA1(), label=None),
    )
    result = data.decode("UTF-8")
    return result


# =========================================================================
# def _update_certificates(self):
#     path = "/v3/certificates"
#     self._certificates.clear()
#     code, message = self.request(path, skip_verify=True)
#     if code != 200:
#         return
#     data = json.loads(message).get("data")
#     for value in data:
#         serial_no = value.get("serial_no")
#         effective_time = value.get("effective_time")
#         expire_time = value.get("expire_time")
#         encrypt_certificate = value.get("encrypt_certificate")
#         algorithm = nonce = associated_data = ciphertext = None
#         if encrypt_certificate:
#             algorithm = encrypt_certificate.get("algorithm")
#             nonce = encrypt_certificate.get("nonce")
#             associated_data = encrypt_certificate.get("associated_data")
#             ciphertext = encrypt_certificate.get("ciphertext")
#         if not (
#             serial_no
#             and effective_time
#             and expire_time
#             and algorithm
#             and nonce
#             and associated_data
#             and ciphertext
#         ):
#             continue
#         cert_str = aes_decrypt(
#             nonce=nonce,
#             ciphertext=ciphertext,
#             associated_data=associated_data,
#             apiv3_key=self._apiv3_key,
#         )
#         certificate = load_certificate(cert_str)
#         if not certificate:
#             continue
#         if int(cryptography_version.split(".")[0]) < 42:
#             now = datetime.utcnow()
#             if (
#                 now < certificate.not_valid_before
#                 or now > certificate.not_valid_after
#             ):
#                 continue
#         else:
#             now = datetime.now(timezone.utc)
#             if (
#                 now < certificate.not_valid_before_utc
#                 or now > certificate.not_valid_after_utc
#             ):
#                 continue
#         self._certificates.append(certificate)
#         if not self._cert_dir:
#             continue
#         if not os.path.exists(self._cert_dir):
#             os.makedirs(self._cert_dir)
#         if not os.path.exists(self._cert_dir + serial_no + ".pem"):
#             with open(self._cert_dir + serial_no + ".pem", "w") as f:
#                 f.write(cert_str)


# def rsa_verify(timestamp, nonce, body, signature, certificate):
#     sign_str = "%s\n%s\n%s\n" % (timestamp, nonce, body)
#     public_key = certificate.public_key()
#     message = sign_str.encode("UTF-8")
#     try:
#         signature = b64decode(signature)
#     except:
#         return False
#     try:
#         public_key.verify(signature, message, PKCS1v15(), SHA256())
#     except InvalidSignature:
#         return False
#     return True


# def _verify_signature(headers, body):
#     """准备验证签名"""
#     signature = headers.get("Wechatpay-Signature")
#     timestamp = headers.get("Wechatpay-Timestamp")
#     nonce = headers.get("Wechatpay-Nonce")
#     serial_no = headers.get("Wechatpay-Serial")
#     cert_found = False
#     for cert in self._certificates:
#         if int("0x" + serial_no, 16) == cert.serial_number:
#             cert_found = True
#             certificate = cert
#             break
#     if not cert_found:
#         self._update_certificates()
#         for cert in self._certificates:
#             if int("0x" + serial_no, 16) == cert.serial_number:
#                 cert_found = True
#                 certificate = cert
#                 break
#         if not cert_found:
#             return False
#     if not rsa_verify(timestamp, nonce, body, signature, certificate):
#         return False
#     return True
